#ifndef BIN_OP_HPP__
#define BIN_OP_HPP__

/** 
 * \file arith.hpp
 * \author Mason Smith <masonium@caltech.edu>
 * defines various arithmetic instructions
 */

#include "vm.hpp"
#include "instr.hpp"
#include <cmath>

namespace BMD
{
  class Value;  

  /**
   * bin_op_instr<typename bin_op_visitor>.
   * Creates an instruction which executes the binary operator visitor
   * on the accumulator and top stack element
   */
  template <typename bin_op_visitor>
  class binary_op_instr : public Instruction
  {
  public:
    void execute(VM* vm)
    {
      Value arg1 = vm->get_accumulator();
      Value arg2 = vm->get_stack_element(0);
      
      Value res = boost::apply_visitor(bin_op_visitor(), arg1, arg2);
      vm->set_accumulator(res);
      vm->pop();
    }
  };

  /**
   * un_op_instr<typename bin_op_visitor>.
   * Creates an instruction which executes the unary operator visitor
   * on the accumulator
   */
  template <typename un_op_visitor>
  class unary_op_instr : public Instruction
  {
  public:
    void execute(VM* vm)
    {
      Value arg1 = vm->get_accumulator();
      
      Value res = boost::apply_visitor(un_op_visitor(), arg1);
      vm->set_accumulator(res);
    }
  };

  /**************************************
   * arithmetic instructions
   *************************************/

  /* Makes a class which does
     some simple binary operation on numbers.
  */
#define MAKE_BINARY_ARITH_INSTR(v, instr, op, name) class v :              \
    public boost::static_visitor<Value>                                 \
  {                                                                     \
  public:                                                               \
    Value operator() (int i, int j) const { return Value(i op j); }     \
    Value operator() (double i, double j) const { return Value(i op j); } \
    Value operator() (int i, double j) const { return Value(i op j); }  \
    Value operator() (double i, int j) const { return Value(i op j); }  \
    BINARY_DEFAULT_TO_TYPE_ERROR(name, name + string(": arguments are not numbers")) \
      virtual ~v() { }                                                  \
  };                                                                    \
  typedef binary_op_instr<v> instr

  /* Makes a class for comparisons on various values */
#define MAKE_COMPARISON_INSTR(v, instr, op, name) class v :             \
    public boost::static_visitor<Value>                                 \
  {                                                                     \
  public:                                                               \
    Value operator() (int i, int j) const { return Value(i op j); }     \
    Value operator() (double i, double j) const { return Value(i op j); } \
    Value operator() (int i, double j) const { return Value(i op j); }  \
    Value operator() (double i, int j) const { return Value(i op j); }  \
    Value operator() (string s, string t) const { return Value(s op t); } \
    Value operator() (ListRef l1, ListRef l2) const { return Value(l1 op l2); } \
  BINARY_DEFAULT_TO_TYPE_ERROR(name, name + string(": arguments are not comparable")) \
      virtual ~v() { }                                                  \
  };                                                                    \
  typedef binary_op_instr<v> instr

  /*************************************
   * basic arithmetic operators
   ************************************/
  MAKE_BINARY_ARITH_INSTR(add_visitor, add_instr, +, "add");
  MAKE_BINARY_ARITH_INSTR(sub_visitor, sub_instr, -, "sub");
  MAKE_BINARY_ARITH_INSTR(mul_visitor, mul_instr, *, "mul");
  
  /*************************************
   * numeric comparisons
   ************************************/
  MAKE_BINARY_ARITH_INSTR(lt_visitor,  lt_instr,  <, "lt");
  MAKE_BINARY_ARITH_INSTR(gt_visitor,  gt_instr,  >, "gt");
  MAKE_BINARY_ARITH_INSTR(lte_visitor, lte_instr, <=, "lte");
  MAKE_BINARY_ARITH_INSTR(gte_visitor, gte_instr, >=, "gte");
  
  /*************************************
   * arbitrary comparisons
   ************************************/
  MAKE_COMPARISON_INSTR(eq_visitor,  eq_instr,  ==, "eq");
  MAKE_COMPARISON_INSTR(neq_visitor, neq_instr, !=, "neq");

  // division needs to be handled separately
  // values with two integer arguments may still need to be promoted
  class div_visitor : public boost::static_visitor<Value>
  {
  public:
    Value operator() (int i, int j) const 
    {
      return Value(double(i) / double(j));
    }
    Value operator() (double i, double j) const
    {
      return Value(i / j);
    }
    Value operator() (int i, double j) const
    {
      return Value(i / j);
    }
    Value operator() (double i, int j) const
    {
      return Value(i / j);
    }
    
    BINARY_DEFAULT_TO_TYPE_ERROR("div", "div: values are not numbers")
  };

  typedef binary_op_instr<div_visitor> div_instr;

  /*
   *Makes a class, which does some simple binary operation on integers.
   */
#define MAKE_INT_BINARY_ARITH_INSTR(v, instr, op, name)                 \
  class v : public boost::static_visitor<Value>                         \
  {                                                                     \
  public:                                                               \
    Value operator() (int i, int j) const { return Value(i op j); }     \
    BINARY_DEFAULT_TO_TYPE_ERROR(name,                                  \
                                 name + string(": arguments are not integers")) \
      virtual ~v() { }                                                  \
  };                                                                    \
  typedef binary_op_instr<v> instr

  MAKE_INT_BINARY_ARITH_INSTR(int_div_visitor, divi_instr, /, "divi");
  MAKE_INT_BINARY_ARITH_INSTR(int_rem_visitor, remi_instr, %, "remi");
  MAKE_INT_BINARY_ARITH_INSTR(and_visitor, and_instr, &&, "remi");
  MAKE_INT_BINARY_ARITH_INSTR(or_visitor, or_instr, ||, "remi");

  /** 
   * neg instruction.
   * take the negative of a number 
   */
  class neg_visitor : public boost::static_visitor<Value>
  {
  public:
    Value operator() (int i) const { return Value(-i); }
    Value operator() (double d) const { return Value(-d); }
    UNARY_DEFAULT_TO_TYPE_ERROR("neg", Value, "neg: argument not a number")
  };

  /**
   * frac instruction.
   * Get the fractional part of the number
   */
  class frac_visitor : public boost::static_visitor<Value>
  {
  public:
    Value operator() (int i) const { return Value(0.0); }
    Value operator() (double d) const { double x; return Value(modf(d, &x)); }
    UNARY_DEFAULT_TO_TYPE_ERROR("frac", Value, "frac: argument not a number")
  };
  
  /**
   * int instruction.
   * Get the integer part of the number
   */
  class int_visitor: public boost::static_visitor<Value>
  {
  public:
    Value operator() (int i) const { return Value(i); }
    Value operator() (double d) const { double x; modf(d, &x); return Value((int)x); }
    UNARY_DEFAULT_TO_TYPE_ERROR("int", Value, "int: argument not a number")
  };

  /**
   * not instruction
   * Get the opposite of an integer, treated as a binary value
   */
  class not_visitor: public boost::static_visitor<Value>
  {
  public:
    Value operator() (int i) const { return Value(!i); }
    UNARY_DEFAULT_TO_TYPE_ERROR("not", Value, "not: argument not an integer")
  };

  typedef unary_op_instr<neg_visitor> neg_instr;
  typedef unary_op_instr<frac_visitor> frac_instr;
  typedef unary_op_instr<int_visitor> int_instr;
  typedef unary_op_instr<not_visitor> not_instr;
}

#endif
